package com.zjw.socket.demo.starter.core;

import com.zjw.socket.demo.starter.annotation.NonNull;
import com.zjw.socket.demo.starter.annotation.Nullable;
import com.zjw.socket.demo.starter.annotation.core.VertxApplication;
import com.zjw.socket.demo.starter.config.ApplicationConfig;
import com.zjw.socket.demo.starter.utils.JarResourcesHolder;
import com.zjw.socket.demo.starter.utils.LogUtils;
import io.vertx.core.Vertx;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;

public final class Reflector {

  private static final String TAG = "ReflectUtils";

  public static String PROJECT_ROOT_PACKAGE_NAME = "";

  @NonNull
  public static Class<?> sApplication;

  public static boolean openIOC;

  @NonNull
  public static Vertx vertx;

  private static final Map<String, Class<?>> mClassCache = new HashMap<>();

  public static void attachVertx(Vertx vertx) {
    Reflector.vertx = vertx;
  }

  public static Class<?> getApplication() {
    return sApplication;
  }

  public static void attachApplication(Class<?> application) {
    VertxApplication vertxApplication = application.getAnnotation(VertxApplication.class);
    if (vertxApplication == null) {
      throw new IllegalArgumentException("none application ");
    }
    Class<?> aClass = vertxApplication.baseClass();
    if (aClass.equals(Class.class)) {
      aClass = application;
    }

    openIOC = vertxApplication.openIOC();
    sApplication = aClass;
    PROJECT_ROOT_PACKAGE_NAME = sApplication.getPackageName();
  }

  /**
   * 在指定路径下找到被注解的类，并以列表的形式返回
   *
   * @param path  指定路径
   * @param clazz 指定类 如果为null表示所有Class类
   */
  public static List<Class<?>> reflectPackageClass(@NonNull String path, @Nullable Class<?> clazz) {
    URL url = findRootURL(path);
    //check jar
    String jarPath = JarResourcesHolder.getJarPath(url);
    if (jarPath == null) {
      return reflectPackageClass(new File(url.getFile()), clazz);
    } else {
      return JarResourcesHolder.resolveClassesFromJarFile(jarPath, path, clazz);
    }
  }

  private static List<Class<?>> reflectPackageClass(@NonNull File rootFile, @Nullable Class<?> clazz) {
    List<Class<?>> result = new ArrayList<>();

    File[] files = rootFile.listFiles();
    if (files == null) {
      LogUtils.error(TAG, "reflectPackageClass files == null");
      return Collections.emptyList();
    }
    for (File f : files) {
      if (f.isFile() && f.getName().endsWith(".class")) {
        String className = parseClassName(f.getPath());
        try {
          Class<?> classType = resolveClass(className);
          if (clazz != null && !isSuperClassOrInterfaceImpl(classType, clazz)) {
            continue;
          }
          result.add(classType);
        } catch (Exception e) {
          LogUtils.error(TAG, "reflectPackageClass:" + e.getMessage());
        }
      } else if (f.isDirectory()) {
        //递归下去
        result.addAll(reflectPackageClass(f, clazz));
      }
    }

    return result;
  }

  /**
   * 在指定路径下找到被注解的类，并以列表的形式返回
   *
   * @param path       指定路径
   * @param annotation 指定注解
   */
  public static List<Class<?>> reflectPackageAnnotationClass(@NonNull String path, @NonNull Class<? extends Annotation> annotation) {
    URL url = findRootURL(path);
    //check jar
    String jarPath = JarResourcesHolder.getJarPath(url);
    if (jarPath == null) {
      return reflectPackageAnnotationClass(new File(url.getFile()), annotation);
    } else {
      return JarResourcesHolder.resolveClassesFromJarByAnnotation(jarPath, path, annotation);
    }
  }

  private static List<Class<?>> reflectPackageAnnotationClass(@NonNull File rootFile, @NonNull Class<? extends Annotation> annotation) {
    List<Class<?>> result = new ArrayList<>();

    File[] files = rootFile.listFiles();
    if (files == null) {
      LogUtils.error(TAG, "reflectPackageAnnotationClassAndInstance files == null");
      return Collections.emptyList();
    }
    for (File f : files) {
      if (f.isFile() && f.getName().endsWith(".class")) {
        String className = parseClassName(f.getPath());
        try {
          Class<?> classType = resolveClass(className);
          if (!classType.isAnnotationPresent(annotation)) {
            continue;
          }
          result.add(classType);
        } catch (Exception e) {
          LogUtils.error(TAG, "reflectPackageAnnotationClassAndInstance:" + e.getMessage());
        }
      } else if (f.isDirectory()) {
        //递归下去
        result.addAll(reflectPackageAnnotationClass(f, annotation));
      }
    }

    return result;
  }


  /**
   * 根据包名找到根file
   *
   * @param path 包名路径，如com.zjw.zy
   * @return 文件File
   * @throws IllegalArgumentException 如果没有找到文件
   */
  @NonNull
  private static URL findRootURL(String path) {
    URL url = sApplication.getClassLoader().getResource(path.replace(".", "/"));
    if (url == null) {
      throw new IllegalArgumentException(TAG + " findRootURL url==null");
    }
    return url;
  }

  /**
   * 返回指定路径下被特殊注解的类实现
   * 需保证存在无参的构造方法
   *
   * @param path       指定路径
   * @param annotation 指定注解
   */
  @NonNull
  public static List<Object> reflectPackageAnnotationClassAndInstance(@NonNull String path, @NonNull Class<? extends Annotation> annotation) {
    URL url = findRootURL(path);
    //check jar
    String jarPath = JarResourcesHolder.getJarPath(url);
    if (jarPath == null) {
      return reflectPackageAnnotationClassAndInstance(new File(url.getFile()), annotation);
    } else {
      return JarResourcesHolder.resolveClassesFromJarByAnnotationAndInstance(jarPath, path, annotation);
    }
  }

  @NonNull
  private static List<Object> reflectPackageAnnotationClassAndInstance(File rootFile, Class<? extends Annotation> annotation) {
    List<Object> result = new ArrayList<>();

    File[] files = rootFile.listFiles();
    if (files == null) {
      LogUtils.error(TAG, "reflectPackageAnnotationClassAndInstance files == null");
      return Collections.emptyList();
    }
    for (File f : files) {
      if (f.isFile() && f.getName().endsWith(".class")) {
        String className = parseClassName(f.getPath());
        try {
          Class<?> classType = resolveClass(className);
          if (!classType.isAnnotationPresent(annotation)) {
            continue;
          }
          Object instance = instanceEmptyConstructor(classType);
          if (instance != null) {
            result.add(instance);
          }
        } catch (Exception e) {
          LogUtils.error(TAG, "reflectPackageAnnotationClassAndInstance:" + e.getMessage());
        }
      } else if (f.isDirectory()) {
        //递归下去
        result.addAll(reflectPackageAnnotationClassAndInstance(f, annotation));
      }
    }

    return result;
  }

  @Nullable
  public static Object instanceEmptyConstructor(@NonNull Class<?> clazz) {
    Constructor<?>[] constructors = clazz.getConstructors();
    if (constructors.length == 0) {
      LogUtils.error(TAG, "invokeEmptyConstructor class" + clazz.getName() + " no constructor");
      return null;
    }

    Constructor<?> constructor = null;
    for (Constructor<?> c : constructors) {
      if (c.getParameterCount() != 0) {
        continue;
      }
      constructor = c;
      break;
    }

    if (constructor == null) {
      LogUtils.error(TAG, "invokeEmptyConstructor class" + clazz.getName() + " no empty params constructor");
      return null;
    }

    try {
      return constructor.newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  private static String parseClassName(String path) {
    String replace = path.replace("\\", ".").replace(".class", "");
    int startIndex = replace.indexOf(PROJECT_ROOT_PACKAGE_NAME);
    if (startIndex == -1) {
      String[] pathList = ApplicationConfig.getPlugPathList();
      for (String s : pathList) {
        startIndex = replace.indexOf(s);
        if (startIndex != -1) {
          break;
        }
      }
    }
    if (startIndex == -1) {
      throw new IllegalArgumentException("can't parse class name " + path);
    }
    return replace.substring(startIndex);
  }

  /**
   * 返回指定路径下符合clazz类实例的列表
   * 保证存在无参的构造方法
   *
   * @param path  指定路径
   * @param clazz 指定类别 可以是接口、超类
   */
  @NonNull
  public static <T> List<T> reflectPackageClassAndInstance(@NonNull String path, @NonNull Class<T> clazz) {
    URL url = findRootURL(path);
    //check jar
    String jarPath = JarResourcesHolder.getJarPath(url);
    if (jarPath == null) {
      return reflectPackageClassAndInstance(new File(url.getFile()), clazz);
    } else {
      return JarResourcesHolder.resolveClassesFromJarFileAndInstance(jarPath, path, clazz);
    }
  }

  @SuppressWarnings("unchecked")
  @NonNull
  private static <T> List<T> reflectPackageClassAndInstance(@NonNull File rootFile, @NonNull Class<T> clazz) {
    List<T> result = new ArrayList<>();

    File[] files = rootFile.listFiles();
    if (files == null) {
      LogUtils.error(TAG, "reflectPackageClassAndInstance files == null");
      return Collections.emptyList();
    }
    for (File f : files) {
      if (f.isFile() && f.getName().endsWith(".class")) {
        String className = parseClassName(f.getPath());
        try {
          Class<?> classType = resolveClass(className);
          if (!isSuperClassOrInterfaceImpl(classType, clazz)) {
            continue;
          }
          Object instance = instanceEmptyConstructor(classType);
          if (instance != null) {
            result.add((T) instance);
          }
        } catch (Exception e) {
          LogUtils.error(TAG, "reflectPackageClassAndInstance:" + e.getMessage());
        }
      } else if (f.isDirectory()) {
        //递归下去
        result.addAll(reflectPackageClassAndInstance(f, clazz));
      }
    }

    return result;
  }

  /**
   * 检查是否为直接或间接实现
   *
   * @param target:检查类
   * @param superType  :超类或接口
   */
  public static boolean isSuperClassOrInterfaceImpl(@NonNull Class<?> target, @NonNull Class<?> superType) {
    Class<?> p = target;

    while (p != null) {
      if (p.equals(superType)) {
        return true;
      }
      Class<?>[] interfaces = p.getInterfaces();
      for (Class<?> anInterface : interfaces) {
        if (anInterface.equals(superType)) {
          return true;
        }
      }
      p = p.getSuperclass();
    }

    return false;
  }


  /**
   * 参数类型的泛型实例，不支持 extends 和 super模式
   *
   * @return class
   */
  @Nullable
  public static Class<?> findRegexClass(String regex) {
    int startIndex = regex.indexOf("<");
    int endIndex = regex.indexOf(">");

    String className = regex.substring(startIndex + 1, endIndex);

    return resolveClass(className);
  }

  @Nullable
  public static Class<?> findRegexClassByContent(Parameter parameter, Class<?> content) {
    String s = parameter.toString();
    String contentName = content.getName();
    int subStart = s.indexOf(contentName);
    String contentRawClass = s.substring(subStart, s.length() - 1);

    return findRegexClass(contentRawClass);
  }

  public static void safeSetFiled(Object o, Field field, Object value) {
    try {
      field.set(o, value);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }


  @Nullable
  public static Object instanceConstructor(Constructor<?> constructor, Object[] instanceParameter) {
    try {
      return constructor.newInstance(instanceParameter);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  @Nullable
  public static Object invokeStaticMethod(Method declaredMethod, Object[] instanceParameter) {
    try {
      return declaredMethod.invoke(null, instanceParameter);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  @Nullable
  public static Object invokeMethod(Object target, Method declaredMethod, Object[] args) {
    try {
      return declaredMethod.invoke(target, args);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 这是一个带缓存容器的策略
   */
  public static Class<?> resolveClass(String className) {
    Class<?> aClass = mClassCache.get(className);
    if (aClass != null) {
      return aClass;
    }
    try {
      aClass = Class.forName(className);
      mClassCache.put(className, aClass);
      return aClass;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}
